package org.orbis.memorycontroller;

import java.util.ArrayList;
import org.msgpack.object.ArrayType;
import org.orbis.communicate.FlagsResult;
import org.orbis.communicate.indexer.term.TermPackage;
import org.orbis.communicate.lock.IndexableSwitch;
import org.orbis.constants.Constants;
import org.orbis.logging.LogParser;
import org.orbis.memoryblock.MemoryBlock;
import org.orbis.memoryblock.appendix.AppendixBlock;
import org.orbis.memoryblock.appendix.AppendixPackage;
import org.orbis.memoryblock.content.ContentBlock;
import org.orbis.memoryblock.content.ContentPackage;
import org.orbis.memoryblock.dictionary.DictionaryBlock;
import org.orbis.memoryblock.dictionary.DictionaryPackage;
import org.orbis.memoryblock.invert.InvertIndexBlock;
import org.orbis.memoryblock.keyvalue.KeyValueBlock;
import org.orbis.memoryblock.keyvalue.KeyValuePackage;
import org.orbis.memoryblock.skippointer.ContentSkipPointerBlock;
import org.orbis.memoryblock.skippointer.SkipPointerBlock;
import org.orbis.memorycontroller.struct.MemoryInfoMaterial;
import org.orbis.memorymanager.ContentInformation;
import org.orbis.memorymanager.MemoryManager;
import org.orbis.memorymanager.filter.DeleteFilter;
import org.orbis.optimizer.DeleteOptimizer;
import org.orbis.optimizer.Optimizer;
import org.orbis.structure.ContentInfo;
import org.orbis.structure.LookupPointer;

public class MemoryController{
	private static final int MEM_DICT = Constants.MEMORY_TYPE_DICT;
	private static final int MEM_INVINDEX = Constants.MEMORY_TYPE_INVINDEX;
	private static final int MEM_SKIPPTR = Constants.MEMORY_TYPE_SKIPPTR;
	private static final int MEM_CONT = Constants.MEMORY_TYPE_CONTENTS;
	private static final int MEM_SKIPCONT = Constants.MEMORY_TYPE_SKIPPTRCONT;
	private static final int MEM_APX = Constants.MEMORY_TYPE_APPENDIX;
	private static final int MEM_KEYVAL = Constants.MEMORY_TYPE_KEYVALUE;

	private enum Operation{
		LOOKUP, APPEND, MODIFY, DELETE,
	}

	// Memory blocks
	private int blockSize;
	private int blockNum;

	// Disk manager
	private DiskController disk;
	private MemoryManager memman;
	private String dir;

	private Optimizer optimizer;
	private MemoryAllocator allocator;

	public MemoryController(int blockSize, int blockNum, DiskController disk, ContentInfo contentInfo,
		int backupInterval, int replicate, int total, int cluster, int uniqueKeyIndex, DeleteFilter filter,
		MemoryManager manager){
		this.blockSize = blockSize;
		this.blockNum = blockNum;
		this.disk = disk;
		this.dir = disk.getDir();
		this.allocator = new MemoryAllocator(contentInfo, replicate, blockSize, uniqueKeyIndex, disk);
		// FIXME: Fix it to be possible to customize
		this.optimizer = new DeleteOptimizer(filter, this, allocator, disk, contentInfo);
		this.memman = manager;

		allocator.initCluster(cluster, total);
	}

	public DictionaryPackage lookupDictionary(LookupPointer ptr){
		if(ptr == null || ptr.equals(LookupPointer.EMPTY))
			throw new NullPointerException("Detected an attempt to read dictionary with null pointer");
		MemoryBlock dicBlock = allocator.dispense(ptr);
		try{
			if(dicBlock != null){
				DictionaryPackage pkg = ((DictionaryBlock)dicBlock).readAll(ptr.point());
				registerProcessLog(Operation.LOOKUP, "lookupDictionary", ptr, pkg.length(), true);
				return pkg;
			}
		}catch(Exception e){
			LogParser.severe(String.format("Error when seek at %s%n", ptr));
		}

		registerProcessLog(Operation.LOOKUP, "lookupDictionary", ptr, 0, false);
		return null;
	}

	// Lookup to get pointer to the next
	public LookupPointer lookupDictionaryNext(LookupPointer ptr){
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(ptr);

		if(dicBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupDictionaryNext", ptr, 0, true);
			return dicBlock.readNextPointer(ptr.point());
		}
		registerProcessLog(Operation.LOOKUP, "lookupDictionaryNext", ptr, 0, false);
		return LookupPointer.EMPTY;
	}

	// Lookup from dictionary pointer to get a another pointer to invert index
	public LookupPointer lookupDictionaryInvIndex(LookupPointer ptr, int valueType){
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(ptr);

		if(dicBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupDictionaryInvIndex", ptr, 0, true);
			return dicBlock.readInvIndex(ptr.point(), valueType);
		}
		registerProcessLog(Operation.LOOKUP, "lookupDictionaryInvIndex", ptr, 0, false);
		return LookupPointer.EMPTY;
	}

	public LookupPointer storeDictionary(DictionaryPackage pkg, int cluster){
		int currentBlock = allocator.getLastPointer(cluster, MEM_DICT, 0).block();
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(cluster, currentBlock);
		// find where to store
		int newPtr = dicBlock.getNewPointer(pkg);
		// LogParser.finest(String.format("NewPtr: %d", newPtr));
		if(newPtr == -1){
			// allocate another block
			dicBlock = (DictionaryBlock)allocator.allocateBlock(MEM_DICT, 0, cluster);
			if(dicBlock != null){
				currentBlock = dicBlock.getID();
				newPtr = dicBlock.getNewPointer(pkg);
			}
		}

		int returnPtr = dicBlock.writeAll(newPtr, pkg);
		registerProcessLog(Operation.APPEND, "StoreDictionary", LookupPointer.valueOf(currentBlock, newPtr),
			returnPtr - newPtr, true);
		allocator.setLastPointer(cluster, MEM_DICT, 0, currentBlock, newPtr);
		return LookupPointer.valueOf(dicBlock.getID(), newPtr).withCluster(cluster);
	}

	// supporting to only overwrite
	public void storeDictionaryPointer(LookupPointer dicPtr, LookupPointer next, int cluster){
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(cluster, dicPtr.block());

		if(dicBlock != null) dicBlock.writeNextPointer(dicPtr.point(), next);

		registerProcessLog(Operation.MODIFY, "storeDictionaryPointer", dicPtr, 0, true);
	}

	// supporting to only overwrite
	public void incrementDocFreq(LookupPointer dicPtr, int cluster){
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(cluster, dicPtr.block());

		if(dicBlock != null) dicBlock.incrementDocFreq(dicPtr.point());
		registerProcessLog(Operation.MODIFY, "incrementDocFreq", dicPtr, 0, true);
	}

	// supporting to only overwrite
	public void storeDictionaryToInvIndex(LookupPointer dicPtr, LookupPointer invIndex, int valueType){
		DictionaryBlock dicBlock = (DictionaryBlock)allocator.dispense(dicPtr);

		if(dicBlock != null) dicBlock.writeInvertIndex(dicPtr.point(), valueType, invIndex);

		registerProcessLog(Operation.MODIFY, "storeDictionaryToInvIndex", dicPtr, 0, true);
	}

	public LookupPointer lookupSkipPointer(int index, int cluster){
		int nextBlockNum = allocator.getFirstPointer(cluster, MEM_SKIPPTR, 0).block();
		SkipPointerBlock skipBlock = null;
		MemoryBlock memBlock = allocator.dispense(cluster, nextBlockNum);
		if(!(memBlock instanceof SkipPointerBlock)) return null;
		// Find a block
		skipBlock = (SkipPointerBlock)memBlock;
		int findLength = 0;
		int length = 0;

		// No skip pointer stored
		if(skipBlock.getLength() == 0){
			registerProcessLog(Operation.LOOKUP, "lookupSkipPointer", LookupPointer.valueOf(nextBlockNum, 0),
				Constants.BYTE_GLOBALPTR, false);
			return null;
		}
		// Find pointer by index
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextBlock();
			skipBlock = (SkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
		}while(nextBlockNum != -1);

		// The index outs of bound
		if(findLength < index || skipBlock == null) return null;

		index -= (findLength - length);
		registerProcessLog(Operation.LOOKUP, "lookupSkipPointer",
			LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR), Constants.BYTE_GLOBALPTR,
			true);
		LookupPointer retptr = skipBlock.getPointer(index);
		if(retptr != null) retptr.withCluster(cluster);

		return retptr;
	}

	@Deprecated
	public LookupPointer lookupContentsSkipPointer(int index, int subtype, int cluster){
		int nextBlockNum = allocator.getFirstPointer(cluster, MEM_SKIPCONT, subtype).block();
		// Find first block
		SkipPointerBlock skipBlock = (SkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
		int findLength = 0;
		int length = 0;

		// No skip pointer stored
		if(skipBlock.getLength() == 0) return null;

		// Find pointer by index
		do{
			length = skipBlock.getLength();
			findLength += length;
			if(findLength >= index) break;
			nextBlockNum = skipBlock.getNextBlock();
			skipBlock = (SkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
		}while(nextBlockNum != -1);

		// The index gets out of bound
		if(findLength < index || skipBlock == null){
			registerProcessLog(Operation.LOOKUP, "lookupContentsSkipPointer (Deprecated)",
				LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR),
				Constants.BYTE_GLOBALPTR, false);
			return null;
		}

		index -= (findLength - length);
		registerProcessLog(Operation.LOOKUP, "lookupContentsSkipPointer (Deprecated)",
			LookupPointer.valueOf(nextBlockNum, index * Constants.BYTE_GLOBALPTR), Constants.BYTE_GLOBALPTR,
			true);
		return skipBlock.getPointer(index);
	}

	public ContentInformation lookupContentsSkipPointer(long ID, int subtype, int cluster){
		if(ID < -1) return null;
		int nextBlockNum = allocator.getFirstPointer(cluster, MEM_SKIPCONT, subtype).block();
		// Find first block
		ContentSkipPointerBlock skipBlock =
			(ContentSkipPointerBlock)allocator.dispense(cluster, nextBlockNum);

		// No skip pointer stored
		if(skipBlock.getOption(ContentSkipPointerBlock.OPT_COUNT) == 0) return null;

		// Find pointer by index
		long currentID = Long.MAX_VALUE;
		long lastID;
		LookupPointer currentPoint = LookupPointer.valueOf(-1, -1);
		LookupPointer prevPoint = LookupPointer.valueOf(-1, -1);
		// int count = -1;
		int count = 0;
		int point = skipBlock.getFinalPoint();
		do{
			prevPoint.copy(currentPoint);
			lastID = currentID;
			// Lookup ID
			currentID = skipBlock.getID(point);
			point += Constants.BYTE_LONGPTR;
			// Lookup pointer to content
			currentPoint = skipBlock.getPoint(point);
			point += Constants.BYTE_GLOBALPTR;
			point -= 2 * (Constants.BYTE_LONGPTR + Constants.BYTE_GLOBALPTR);
//			System.out.printf("[DEBUG]Input: (%d, %d) -> ID: %d Point: %s%n", nextBlockNum, point + 14,
//			currentID, (currentPoint == null? "null": currentPoint.toString()));
//			System.out.printf("[DEBUG] ID-%d currentID-%d lastID-%d Point-%d%n", ID, currentID, lastID, point);
			// if(currentID == -1){
			if(point < 0){
				if(ID < currentID) count = -1;
				// Find previous block
				// nextBlockNum = skipBlock.getOption(ContentSkipPointerBlock.OPT_NBLOCK);
				nextBlockNum = skipBlock.getOption(ContentSkipPointerBlock.OPT_PBLOCK);
				if(nextBlockNum == -1)
					return new ContentInformation(currentPoint).first().skipptr(count).ID(currentID);
				skipBlock = (ContentSkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
			}
			registerProcessLog(Operation.LOOKUP, "lookupContentsSkipPointer",
				LookupPointer.valueOf(nextBlockNum, point),
				Constants.BYTE_GLOBALPTR + Constants.BYTE_LONGPTR, true);

			// if(currentID > ID) break;
			if(currentID <= ID && ID < lastID) break;
			count++;
		}while(nextBlockNum != -1);
		ContentInformation information;
		// if(count == -1) information = new
		// ContentInformation(currentPoint).first().skipptr(-1).ID(currentID);
		// else information =
		// new ContentInformation(currentPoint).middle(currentPoint).skipptr(count).ID(currentID);
		// if(count == -1) information = new ContentInformation(prevPoint).first().skipptr(-1).ID(lastID);
		information = new ContentInformation(currentPoint).last(prevPoint).skipptr(count).ID(currentID);

		// if(prevPoint.equals(LookupPointer.EMPTY)) prevPoint = null;
		return information;
	}

	// For term skip pointer
	public int getSkipPointerLength(int cluster){
		SkipPointerBlock skipBlock;
		int findIndex = 0;
		int currentBlock = allocator.getFirstPointer(cluster, MEM_SKIPPTR, 0).block();

		// Find blocks by connection
		do{
			skipBlock = (SkipPointerBlock)allocator.dispense(cluster, currentBlock);
			findIndex += skipBlock.getLength();
			currentBlock = skipBlock.getNextBlock();
		}while(currentBlock != -1);
		return findIndex;
	}

	// For content skip pointer
	public int getSkipContentsLength(int subtype, int cluster){
		ContentSkipPointerBlock skipBlock;
		int findIndex = 0;
		int currentBlock = allocator.getFirstPointer(cluster, MEM_SKIPCONT, subtype).block();

		// Find a block to stock
		do{
			skipBlock = (ContentSkipPointerBlock)allocator.dispense(cluster, currentBlock);
			findIndex += skipBlock.getOption(ContentSkipPointerBlock.OPT_COUNT);
			currentBlock = skipBlock.getOption(ContentSkipPointerBlock.OPT_NBLOCK);
		}while(currentBlock != -1);

		return findIndex;
	}

	public synchronized void storeSkipPointer(int pointIndex, int subtype, LookupPointer ptr,
		boolean isModify, int indexType, int cluster){
		try{
			int nextBlockNum = allocator.getFirstPointer(cluster, indexType, subtype).block();
			int findIndex = 0;
			int setIndex = pointIndex;
			SkipPointerBlock skipBlock = null;
			SkipPointerBlock setBlock = null;

			// Find where to stock
			do{
				skipBlock = (SkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
				int length = skipBlock.getLength();
				findIndex += length;
				if(findIndex - pointIndex >= 0){
					setBlock = skipBlock;
					break;
				}
				setIndex -= length;
				nextBlockNum = skipBlock.getNextBlock();
			}while(nextBlockNum != -1);

			// Set at new block
			if(setBlock == null)
				setBlock = (SkipPointerBlock)allocator.allocateBlock(indexType, subtype, cluster);

			// Set and move pointer
			if(setBlock != null){
				if(isModify) setBlock.setPointer(setIndex, ptr, isModify);
				else{
					byte[] moveData = null;
					moveData = setBlock.setPointer(setIndex, ptr, isModify);
					if(moveData != null){
						do{
							nextBlockNum = skipBlock.getNextBlock();
							// Allocate new block
							if(nextBlockNum == -1) setBlock =
								(SkipPointerBlock)allocator.allocateBlock(indexType, subtype, cluster);
							else setBlock = (SkipPointerBlock)allocator.dispense(cluster, nextBlockNum);
							moveData = setBlock.movePointerRep(moveData);
						}while(moveData != null);
					}
				}
			}

			if(!isModify || pointIndex == 0){
				LookupPointer modPtr = allocator.getLastPointer(cluster, indexType, subtype);

				registerProcessLog(Operation.APPEND, "storeSkipPointer", modPtr, Constants.BYTE_GLOBALPTR,
					true);

				if(nextBlockNum != modPtr.block()) modPtr.modify(nextBlockNum, Constants.BYTE_GLOBALPTR);
				else modPtr.modify(modPtr.block(), modPtr.point() + Constants.BYTE_GLOBALPTR);
			}
		}catch(Exception e){
			// It's rare to have an exception
			e.printStackTrace();
		}
	}

	public void storeContentSkipPointer(int subtype, long ID, LookupPointer ptr, int cluster){
		int blockNumber = allocator.getFirstPointer(cluster, MEM_SKIPCONT, subtype).block();

		ContentSkipPointerBlock skipBlock = (ContentSkipPointerBlock)allocator.dispense(cluster, blockNumber);
		ContentSkipPointerBlock anotherBlock;

		int point = skipBlock.allocate();
		if(point == -1){
			// Allocate another block
			anotherBlock = (ContentSkipPointerBlock)allocator.allocateBlock(MEM_SKIPCONT, subtype, cluster);
			int anotherBlockNumber = anotherBlock.getID();
			skipBlock.setOption(ContentSkipPointerBlock.OPT_NBLOCK, anotherBlockNumber);
			skipBlock = anotherBlock;
			point = skipBlock.allocate();
		}

		// System.out.println("[DEBUG] Setting pointer: ID-" + ID + " Pointer-" + ptr);
		skipBlock.setPointer(ID, ptr, point);
		registerProcessLog(Operation.APPEND, "storeContentSkipPointer",
			LookupPointer.valueOf(skipBlock.getID(), point), Constants.BYTE_GLOBALPTR
				+ Constants.BYTE_LONGPTR, true);
	}

	public void modifyContentSkipPointer(int subtype, long ID, LookupPointer ptr, int cluster, int index){
		int blockNumber = allocator.getFirstPointer(cluster, MEM_SKIPCONT, subtype).block();

		ContentSkipPointerBlock skipBlock = (ContentSkipPointerBlock)allocator.dispense(cluster, blockNumber);

		int result;
		do{
			result = skipBlock.modifyPointer(ID, ptr, index);
			index = result;
			if(result != 0){
				// Obtain next block
				blockNumber = skipBlock.getOption(ContentSkipPointerBlock.OPT_NBLOCK);
				if(blockNumber == -1) break;
				skipBlock = (ContentSkipPointerBlock)allocator.dispense(cluster, blockNumber);
			}else{
				registerProcessLog(
					Operation.MODIFY,
					"modifyContentSkipPointer",
					LookupPointer.valueOf(skipBlock.getID(), index
						* (Constants.BYTE_GLOBALPTR + Constants.BYTE_LONGPTR)), Constants.BYTE_GLOBALPTR
						+ Constants.BYTE_LONGPTR, true);

			}
		}while(result != 0);
	}

	public FlagsResult[] lookupInvertIndex(LookupPointer ptr){
		InvertIndexBlock invBlock = (InvertIndexBlock)allocator.dispense(ptr);

		if(invBlock != null){
			FlagsResult[] data = ((InvertIndexBlock)invBlock).read(ptr.point());
			registerProcessLog(Operation.LOOKUP, "lookupInvertIndex", ptr, 0, true);
			return data;
		}

		registerProcessLog(Operation.LOOKUP, "lookupInvertIndex", ptr, 0, false);
		return null;
	}

	public LookupPointer allocateInvertIndex(int length, int cluster){
		int currentBlock = allocator.getLastPointer(cluster, MEM_INVINDEX, 0).block();
		InvertIndexBlock invBlock = (InvertIndexBlock)allocator.dispense(cluster, currentBlock);

		int point = invBlock.allocate();
		if(point == -1){
			// allocate another block
			invBlock = (InvertIndexBlock)allocator.allocateBlock(MEM_INVINDEX, 0, cluster);
			if(invBlock != null){
				currentBlock = invBlock.getID();
				point = invBlock.allocate();
			}
		}

		allocator.setLastPointer(cluster, MEM_INVINDEX, 0, currentBlock, point + invBlock.length());
		registerProcessLog(Operation.APPEND, "allocateInvertIndex",
			LookupPointer.valueOf(currentBlock, point), 0, true);
		return LookupPointer.valueOf(currentBlock, point).withCluster(cluster);
	}

	public LookupPointer storeInvertIndex(LookupPointer ptr, TermPackage pkg){
		try{
			InvertIndexBlock invBlock = (InvertIndexBlock)allocator.dispense(ptr);
			if(invBlock != null){
				invBlock.write(ptr.point(), pkg);
				registerProcessLog(Operation.APPEND, "storeInvertIndex", ptr, 0, true);
			}
			return ptr;
		}catch(ClassCastException e){
			System.out.println("We tried to look up (" + ptr.cluster() + ") " + ptr + " but error occurs.");
			// e.printStackTrace();
			return null;
		}
	}

	public String lookupContents(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);
		if(contBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupContents", ptr, 0, true);
			return contBlock.readString(ptr.point());
		}

		registerProcessLog(Operation.LOOKUP, "lookupContents", ptr, 0, false);
		return null;
	}

	public LookupPointer lookupContentNext(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);
		if(contBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupContents", ptr, 0, true);
			return contBlock.readNext(ptr.point());
		}

		registerProcessLog(Operation.LOOKUP, "lookupContents", ptr, 0, false);
		return null;
	}

	public ContentPackage lookupContentsAll(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);
		if(contBlock != null) return contBlock.readAll(ptr.point());

		registerProcessLog(Operation.LOOKUP, "lookupContentsAll", ptr, 0, false);
		return null;
	}

	public LookupPointer[] lookupContentToAppendixPointer(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);

		if(contBlock != null){
			LookupPointer[] ptrs = contBlock.readAppendixPointers(ptr.point(), ptr.cluster());
			registerProcessLog(Operation.MODIFY, "lookupContentToAppendixPointer", ptr, 0, true);
			return ptrs;
		}

		registerProcessLog(Operation.MODIFY, "lookupContentToAppendixPointer", ptr, 0, false);
		return null;
	}

	public boolean isContentDeleted(LookupPointer ptr){
		return false;
	}

	public long lookupContentID(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);

		if(contBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupContentID", ptr, 8, true);
			return contBlock.readID(ptr.point());
		}

		registerProcessLog(Operation.LOOKUP, "lookupContentID", ptr, 0, false);
		return -1;
	}

	@Deprecated
	public LookupPointer lookupNextContentPointer(LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);

		if(contBlock != null){
			int result = contBlock.getNextOne(ptr.point());
			if(result == -1){
				// If pointer reaches last, returns nothing
				if(ptr.block() == allocator.getLastPointer(ptr.cluster(), MEM_CONT,
					contBlock.getOption(ContentBlock.OPT_TYPE)).block()) return null;
				// Else returns top of next block
				else return LookupPointer.valueOf(contBlock.getOption(ContentBlock.OPT_NEXT), 0).withCluster(
					ptr.cluster());
			}
			registerProcessLog(Operation.LOOKUP, "lookupNextContentPointer", ptr, 0, true);
			return LookupPointer.valueOf(ptr.block(), result).withCluster(ptr.cluster());
		}

		registerProcessLog(Operation.LOOKUP, "lookupNextContentPointer", ptr, 0, false);
		return null;
	}

	public LookupPointer storeContent(ContentPackage pkg, int subtype){
		int currentID = allocator.getLastPointer(pkg.getCluster(), MEM_CONT, subtype).block();
		ContentBlock contBlock = (ContentBlock)allocator.dispense(pkg.getCluster(), currentID);
		if(contBlock == null){
			LogParser.warning("error occurs when reading "
				+ allocator.getLastPointer(pkg.getCluster(), MEM_CONT, subtype));
			throw new IndexOutOfBoundsException();
		}

		// check where to store
		int newPtr = contBlock.getNewPointer(pkg);
		if(newPtr == -1){
			// not sufficient to store on current block
			contBlock = (ContentBlock)allocator.allocateBlock(MEM_CONT, subtype, pkg.getCluster());
			if(contBlock != null){
				currentID = contBlock.getID();
				newPtr = contBlock.getNewPointer(pkg);
			}
		}
		int newPoint = contBlock.writeAll(newPtr, pkg);
		registerProcessLog(Operation.APPEND, "storeContents", LookupPointer.valueOf(currentID, newPtr),
			newPoint - newPtr, true);
		allocator.setLastPointer(pkg.getCluster(), MEM_CONT, subtype, currentID, newPoint);
		return LookupPointer.valueOf(contBlock.getID(), newPtr).withCluster(pkg.getCluster());
	}

	public void storeContentNextPointer(LookupPointer next, LookupPointer ptr){
		ContentBlock contentBlock = (ContentBlock)allocator.dispense(ptr);
		if(contentBlock == null) throw new NullPointerException();

		contentBlock.modifyNextPointer(ptr.point(), next);

		registerProcessLog(Operation.MODIFY, "storeContents", ptr, Constants.BYTE_GLOBALPTR, true);
	}

	public void storeContentToAppPtr(LookupPointer[] appendixPointer, LookupPointer ptr){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(ptr);

		if(contBlock == null) throw new NullPointerException();

		for(int i = 0; i < appendixPointer.length; i++){
			contBlock.writeAppendixPointer(ptr.point(), i, appendixPointer[i]);
		}
		registerProcessLog(Operation.MODIFY, "storeContentToAppPtr", ptr, Constants.BYTE_GLOBALPTR
			* appendixPointer.length, true);
	}

	public void changeContentReferenceCount(int cluster, int block, int difference){
		ContentBlock contBlock = (ContentBlock)allocator.dispense(cluster, block);

		if(contBlock == null) return;

		contBlock.setReferenceCount(difference);
	}

	public void changeAppendixReferenceCount(int cluster, int block, int difference){
		AppendixBlock appBlock = (AppendixBlock)allocator.dispense(cluster, block);

		if(appBlock == null) return;

		appBlock.setReferenceCount(difference);
	}

	public AppendixPackage lookupAppendix(LookupPointer ptr){
		AppendixBlock appBlock = (AppendixBlock)allocator.dispense(ptr);

		if(appBlock != null){
			registerProcessLog(Operation.LOOKUP, "lookupAppendix", ptr, 0, true);
			return appBlock.read(ptr.point());
		}

		registerProcessLog(Operation.LOOKUP, "lookupAppendix", ptr, 0, false);
		return null;
	}

	public LookupPointer storeAppendix(AppendixPackage pkg, int subtype){
		int currentID = allocator.getLastPointer(pkg.getCluster(), MEM_APX, subtype).block();
		AppendixBlock appBlock = (AppendixBlock)allocator.dispense(pkg.getCluster(), currentID);
		int returnPtr;

		// check where to store
		int newPtr = appBlock.getNewPointer(pkg);
		if(newPtr == -1){
			// allocate another block
			appBlock = (AppendixBlock)allocator.allocateBlock(MEM_APX, subtype, pkg.getCluster());
			if(appBlock != null){
				currentID = appBlock.getID();
				newPtr = appBlock.getNewPointer(pkg);
			}
		}

		returnPtr = appBlock.write(newPtr, pkg);
		registerProcessLog(Operation.APPEND, "storeAppendix", LookupPointer.valueOf(currentID, newPtr),
			returnPtr - newPtr, true);
		allocator.setLastPointer(pkg.getCluster(), MEM_APX, subtype, currentID, returnPtr);
		return LookupPointer.valueOf(appBlock.getID(), newPtr).withCluster(pkg.getCluster());
	}

	public KeyValuePackage lookupKeyValue(LookupPointer ptr){
		KeyValueBlock block = (KeyValueBlock)allocator.dispense(ptr);

		if(block == null) return null;

		return block.read(ptr.point());
	}

	public void storeKeyValue(int cluster, long key, long value, LookupPointer next){
		int currentID = allocator.getLastPointer(cluster, MEM_KEYVAL, 0).block();
		KeyValueBlock block = (KeyValueBlock)allocator.dispense(cluster, currentID);
		int returnPtr;

		// check where to store
		int newPtr = block.allocate();
		if(newPtr == -1){
			// allocate another block
			block = (KeyValueBlock)allocator.allocateBlock(MEM_KEYVAL, 0, cluster);
			if(block != null){
				currentID = block.getID();
				newPtr = block.allocate();
			}
		}

		returnPtr = block.write(newPtr, KeyValuePackage.getPackage(key, value, next));
		allocator.setLastPointer(cluster, MEM_KEYVAL, 0, currentID, returnPtr);
	}

	// Lookup without specifying data type
	public byte[] lookupRawData(LookupPointer ptr, int length){
		MemoryBlock memBlock = allocator.dispense(ptr);

		if(memBlock != null){
			// on memory
			return memBlock.read(ptr.point(), length);
		}
		return null;
	}

	public void resetBlockVacancy(int block, int cluster){
		allocator.dispense(cluster, block).resetBlockVacancy();
	}

	public int checkFreeSpace(){
		return allocator.checkFreeSpace();
	}

	public ArrayList<MemoryInfoMaterial> getStatusList(){
		return allocator.getMemStatus();
	}

	public void showMemoryElapsed(){
		allocator.showMemoryElapsed();
	}

	public void saveToMemory(){
		allocator.saveAllMemory();
	}

	public void addCluster(int myOrder, int totalNodes){
		allocator.renumberCluster(myOrder, totalNodes);
	}

	public int getBlockSize(){
		return blockSize;
	}

	public int getBlockNum(){
		return blockNum;
	}

	public int getContentCount() throws Exception{
		return allocator.getContentCount();
	}

	public int getTermCount() throws Exception{
		return allocator.getTermCount();
	}

	public void optimizeMemory(){
		optimizer.optimize();
	}

	public int getFileNum(){
		return disk.getItemNum();
	}

	public String getDirectory(){
		return dir;
	}

	public void backup(){
		IndexableSwitch.lock();
		// backuper.toFileLatest();
		try{
			IndexableSwitch.unlock();
		}catch(InterruptedException e){}
	}

	public void doRevertFromFile(long ID) throws Exception{
//		allocator.doRevertFromFile(ID);
	}

	public boolean isOnMemory(int index, int cluster){
		return allocator.isOnMemory(index, cluster);
	}

	public void registerProcessLog(Operation op, String opeName, LookupPointer from, int length,
		boolean succeed){
		// TODO: Now it only elapses operation logs to util.logging
		String succeedStr = (succeed? "OK": "NG");
		String operation = null;
		switch(op){
		case LOOKUP:
			operation = "Lookup";
			break;
		case APPEND:
			operation = "Append";
			break;
		case MODIFY:
			operation = "Modify";
			break;
		case DELETE:
			operation = "Delete";
			break;
		}
		LogParser.finer(String.format("%s/%s: (%d, %d) -> (%d, %d) %s%n", operation, opeName, from.block(),
			from.point(), from.block(), from.point() + length, succeedStr));
	}

	public void readMemory(){
		allocator.readAll();
	}

	public void suspendMyNode(){
		memman.suspendMyNode();
	}

	public void resumeMyNode(){
		memman.resumeMyNode();
	}

	public int getClusterBlockAmount(int clusterID){
		return allocator.getClusterBlockAmount(clusterID);
	}

	public void setClusterPointInfo(ArrayType info){
		allocator.setClusterPointInfo(info);
	}

	public ArrayType getClusterPointInfo(int clusterID){
		return allocator.getClusterPointInfo(clusterID);
	}

	public ArrayType toArrayType(int cluster, int ID){
		MemoryBlock blk = allocator.dispense(cluster, ID);
		if(blk == null) return null;
		return blk.toArrayType();
	}

	public void fromArrayType(int cluster, int ID, int type, int subtype, byte[] array){
		MemoryBlock blk = allocator.dispense(cluster, ID);
		if(blk == null) blk = allocator.allocateBlock(type, subtype, cluster, ID);
		blk.fromRawType(array);
	}
}
